package com.hj.springframework2.utils;

import com.hj.springframework2.annotation.Bean;
import com.hj.springframework2.annotation.Component;
import com.hj.springframework2.annotation.Value;
import com.hj.springframework2.exception.BeanDefinitionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @Author : hujing
 * @Date: 2025/6/13 16:06
 * @Description:
 */
public class ClassUtils {

    final static Logger logger = LoggerFactory.getLogger(ClassUtils.class);

    /**
     * Get non-arg method by @PostConstruct or @PreDestroy. Not search in super class.
     *
     * <code>
     * @PostConstruct 
     * void init() {}
     * </code>
     */
    public static Method findAnnotionMethod(Class<?> clazz,Class<? extends Annotation> annoClass){
        List<Method> ms = Arrays.stream(clazz.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(annoClass)).map(m -> {
            if (m.getParameterCount() != 0) {
                throw new BeanDefinitionException(
                        String.format("Method '%s' with @%s must not have argument: %s", m.getName(), annoClass.getSimpleName(), clazz.getName()));
            }
            return m;
        }).toList();
        if (ms.isEmpty()){
            return null;
        }
        if (ms.size() == 1){
            return ms.get(0);
        }
        throw new BeanDefinitionException(String.format("Multiple methods with @%s found in class: %s", annoClass.getSimpleName(), clazz.getName()));
    }

    /**
     * Get bean name by:
     *
     * <code>
     * @Bean
     * Hello createHello() {}
     * </code>
     */
    public static String getBeanName(Method method){
        Bean bean = method.getAnnotation(Bean.class);
        String name = bean.value();
        if (name.isEmpty()){
            name = method.getName();
        }
        return name;
    }
    
    /**
     * Get bean name by:
     *
     * <code>
     *
     * @Component public class Hello {}
     * </code>
     */
    public static String getBeanName(Class<?> clazz) {
        String name = "";
        Component component = clazz.getAnnotation(Component.class);
        if (component != null) {
            name = component.value();
        } else {
            // 未找到@Component，继续在其他注解中查找@Component
            for (Annotation anno : clazz.getAnnotations()) {
                if (findAnnotation(anno.annotationType(), Component.class) != null) {
                    try {
                        name = (String) anno.annotationType().getMethod("value").invoke(anno);
                    } catch (ReflectiveOperationException e) {
                        throw new BeanDefinitionException("Cannot get annotation value", e);
                    }
                }
            }
        }
        if (name.isEmpty()){
            // default name: "HelloWorld" => "helloWorld"
            name = clazz.getSimpleName();
            name = Character.toLowerCase(name.charAt(0))+name.substring(1);
        }
        return name;
    }

    /**
     * @param target    要查找注解的目标类
     * @param annoClass 要查找的注解类型
     * @param <A>       注解类型
     * @return
     */
    public static <A extends Annotation> A findAnnotation(Class<?> target, Class<A> annoClass) {
        // 从目标类上直接获取要查找的注解类型
        A a = target.getAnnotation(annoClass);
        for (Annotation anno : target.getAnnotations()) {
            Class<? extends Annotation> annoType = anno.annotationType();
            if (!annoType.getPackageName().equals("java.lang.annotation")) {
                // 递归查找注解
                A found = findAnnotation(annoType, annoClass);
                if (found != null) {
                    if (a != null) {
                        logger.error("Duplicate @" + annoClass.getSimpleName() + " found on class " + target.getSimpleName());
                    }
                    a = found;
                }
            }
        }
        return a;
    }

    @Nullable
    public static <A extends Annotation> A getAnnotation(Annotation[] annos,Class<A> annoClass) {
        for (Annotation anno : annos) {
            if (annoClass.isInstance(anno)){
                return (A) anno;
            }
        }
        return null;
    }
}
